package com.hzqy.web.filter;

import com.alibaba.fastjson.JSONObject;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.URLDecoder;
import java.util.*;
import java.util.regex.Pattern;

public class ParameterSecurityCheckFilter implements Filter{
	private final static Logger LOG = LoggerFactory.getLogger(ParameterSecurityCheckFilter.class);

	private static final List<String> INVALID_STRING_LIST;
	private static final Pattern SPECIAL_CHARACTERS=Pattern.compile("[()（）<>/\\\\{}\\+='\";\\[\\]|&$#@.]", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);
	private static final Pattern SPECIAL_CHARACTERS_FOR_JSON=Pattern.compile("[()（）<>/\\\\\\+=';\\[\\]|&$#@]", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);

	//防止sql注入正则表达式
	private static final Pattern PREVENT_SQL_INJECT_REGREP =
			Pattern.compile(
					"(?:')|(?:--)|(/\\*(?:.|[\\n\\r])*?\\*/)|(\\b(select|update|union|and|or|delete|insert|trancate|char|into|substr|ascii|declare|exec|count|master|into|drop|execute)\\b)",
					Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL | Pattern.UNICODE_CASE
			);

	//防止xss注入正则表达式
	private static final Pattern PREVENT_XSS_INJECT_REGREP =
			Pattern.compile(
					//"[%--`~!@#$^&*()=|{}':;',\\\\[\\\\].<>/?~！@#￥……&*（）——| {}【】‘；：”“'。，、？]";
					"[#]",
					Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL
			);

	private static Pattern[] PREVENT_XSS_INJECT_REGREPS = new Pattern[]{
			// Script fragments
			Pattern.compile("<script>(.*?)</script>", Pattern.CASE_INSENSITIVE),
			// src='...'
			Pattern.compile("src[\r\n]*=[\r\n]*\\\'(.*?)\\\'", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL),
			Pattern.compile("src[\r\n]*=[\r\n]*\\\"(.*?)\\\"", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL),
			// lonely script tags
			Pattern.compile("</script>", Pattern.CASE_INSENSITIVE),
			Pattern.compile("<script(.*?)>", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL),
			// eval(...)
			Pattern.compile("eval\\((.*?)\\)", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL),
			// expression(...)
			Pattern.compile("expression\\((.*?)\\)", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL),
			// javascript:...
			Pattern.compile("javascript:", Pattern.CASE_INSENSITIVE),
			// vbscript:...
			Pattern.compile("vbscript:", Pattern.CASE_INSENSITIVE),
			// onl oad(...)=...
			Pattern.compile("onload(.*?)=", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL),
			//现场安全测试增加校验
			Pattern.compile("alert(.*?)", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL),
			Pattern.compile("<", Pattern.MULTILINE | Pattern.DOTALL),
			Pattern.compile(">", Pattern.MULTILINE | Pattern.DOTALL),
			// Pattern.Pattern("(document|onload|eval|script|img|svg|onerror|javascript|alert)\\\\b")
			Pattern.compile("((alert|on\\w+|function\\s+\\w+)\\s*\\(\\s*(['+\\d\\w](,?\\s*['+\\d\\w]*)*)*\\s*\\))"),
			//Checks any html tags i.e. <script, <embed, <object etc.
			Pattern.compile("(<(script|iframe|embed|frame|frameset|object|img|applet|body|html|style|layer|link|ilayer|meta|bgsound))", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE),
			// 换行 回车
			Pattern.compile("((\r)|(\n))", Pattern.CASE_INSENSITIVE | Pattern.DOTALL)
			//Pattern.compile("[()（）/\\\\{}\\-='\";\\[\\]&$#@.。]", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL)

	};

	// 一些带特殊符号，但是合法的值, 大小写都允许放通
	private static String[] VALID_SPECIAL_VALUES = new String[] {
		"utf-8"
	};

	// 忽略xss检测字段
    private static Pattern IGNORE_XSS_FIELD = Pattern.compile("(f_cc_desc|f_sc_value|picJson)");


	static {
		String[] string_arr = new String[] {
				"--", "//", "#",
				"xp_shell", "||", "'",
				"\""

		};

		INVALID_STRING_LIST = new ArrayList<>(string_arr.length);
		for(String word : string_arr) {
			INVALID_STRING_LIST.add(word);
		}

	}


	private final boolean isSpecial(String str) {
		for (String validSpecialValue : VALID_SPECIAL_VALUES) {
			if (validSpecialValue.equalsIgnoreCase(str)) {
				return true;
			}
		}

		return false;
	}

	private final int checkString(String key, String str,String uri) {
		// 空值不过滤
		if(str == null || "".equals(str.trim())) {
			return 0;
		}
		// 特殊值不过滤
		if (isSpecial(str)) {
			return 0;
		}

		// uri是否合法
		if (isUriInValid(uri)) {
			return -4;
		}

		if(PREVENT_SQL_INJECT_REGREP.matcher(str).find()) {
			LOG.debug("检测出sql_inject攻击 pattern:" + PREVENT_SQL_INJECT_REGREP.pattern() + " value: " + str);
			return -3;
		}

		// 忽略xss校验的字段可以不做校验
		if (!IGNORE_XSS_FIELD.matcher(key).find()) {
		    // 无论.jsp还是.do都过滤参数
            for (Pattern scriptPattern : PREVENT_XSS_INJECT_REGREPS) {
                if(scriptPattern.matcher(str).find()) {
                    LOG.debug("检测出xss攻击 pattern:" + scriptPattern.pattern() + " value: " + str);
                    return -2;
                }
            }

            // 只对需要验证的请求过滤
            if(needCheckSpecialCharactorAction(uri)){
                if(SPECIAL_CHARACTERS.matcher(str).find()) {
					LOG.debug("检测出xss特殊字符 pattern:" + SPECIAL_CHARACTERS.pattern() + " value: " + str);
                    return -2;
                }
            }
        }
		try {
			str=URLDecoder.decode(str,"utf-8");
			// 特殊值不过滤
			if (isSpecial(str)) {
				return 0;
			}

			// uri是否合法
			if (isUriInValid(uri)) {
				return -4;
			}

			if(PREVENT_SQL_INJECT_REGREP.matcher(str).find()) {
				LOG.debug("检测出sql_inject攻击 pattern:" + PREVENT_SQL_INJECT_REGREP.pattern() + " value: " + str);
				return -3;
			}

			// 忽略xss校验的字段可以不做校验
			if (!IGNORE_XSS_FIELD.matcher(key).find()) {
				// 无论.jsp还是.do都过滤参数
				for (Pattern scriptPattern : PREVENT_XSS_INJECT_REGREPS) {
					if(scriptPattern.matcher(str).find()) {
						LOG.debug("检测出xss攻击 pattern:" + scriptPattern.pattern() + " value: " + str);
						return -2;
					}
				}

				// 只对需要验证的请求过滤
				if(needCheckSpecialCharactorAction(uri)){
					if(SPECIAL_CHARACTERS.matcher(str).find()) {
						LOG.debug("检测出xss特殊字符 pattern:" + SPECIAL_CHARACTERS.pattern() + " value: " + str);
						return -2;
					}
				}
			}
		}catch (Exception e){
			LOG.debug(str+"解析异常");
			return -2;
		}
		return 0;
	}

	private final int checkPostString(String str, String uri) {
		// 空值不过滤
		if(str == null || "".equals(str.trim())) {
			return 0;
		}

		// 特殊值不过滤
		if (isSpecial(str)) {
			return 0;
		}

		// uri是否合法
		if (isUriInValid(uri)) {
			return -4;
		}

		if(PREVENT_SQL_INJECT_REGREP.matcher(str).find()) {
			LOG.debug("检测出sql_inject攻击 pattern:" + PREVENT_SQL_INJECT_REGREP.pattern() + " value: " + str);
			return -3;
		}


		for (Pattern scriptPattern : PREVENT_XSS_INJECT_REGREPS) {
			if(scriptPattern.matcher(str).find()) {
				LOG.debug("检测出xss攻击 pattern:" + scriptPattern.pattern() + " value: " + str);
				return -2;
			}
		}

		// 只对需要验证的请求过滤
		if(SPECIAL_CHARACTERS_FOR_JSON.matcher(str).find()) {
			LOG.debug("检测出xss特殊字符 pattern:" + SPECIAL_CHARACTERS_FOR_JSON.pattern() + " value: " + str);
			return -2;
		}
		try{
			str=URLDecoder.decode(str,"utf-8");
			// 特殊值不过滤
			if (isSpecial(str)) {
				return 0;
			}

			// uri是否合法
			if (isUriInValid(uri)) {
				return -4;
			}

			if(PREVENT_SQL_INJECT_REGREP.matcher(str).find()) {
				LOG.debug("检测出sql_inject攻击 pattern:" + PREVENT_SQL_INJECT_REGREP.pattern() + " value: " + str);
				return -3;
			}


			for (Pattern scriptPattern : PREVENT_XSS_INJECT_REGREPS) {
				if(scriptPattern.matcher(str).find()) {
					LOG.debug("检测出xss攻击 pattern:" + scriptPattern.pattern() + " value: " + str);
					return -2;
				}
			}

			// 只对需要验证的请求过滤
			if(SPECIAL_CHARACTERS_FOR_JSON.matcher(str).find()) {
				LOG.debug("检测出xss特殊字符 pattern:" + SPECIAL_CHARACTERS_FOR_JSON.pattern() + " value: " + str);
				return -2;
			}
		}catch (Exception e){
			LOG.debug(str+"解析异常");
			return -2;
		}

		return 0;
	}



	private boolean isUriInValid(String uri) {
		return uri.endsWith(".action");
	}


	// 需要过滤特殊字符的.do请求, 其他请求很有可能携带特殊字符，比如日期 2021-06-22 09:06:53, 比如 f_cc_desc含有html标签, 比如 encode=utf-8 ...... 这里无法列举所有情况
	private static Pattern[] FILTER_SPECIAL_CHARACTER_ACTION_SUFFIX = new Pattern[] {
			Pattern.compile("((main.do)|(login.do))$", Pattern.CASE_INSENSITIVE | Pattern.DOTALL)
	};

	private boolean needCheckSpecialCharactorAction(String uri) {
		// jsp、js、css 都要验证
		if (uri.endsWith(".jsp") || uri.endsWith(".css") || uri.endsWith(".js")) return true;
		// 无后缀的请求，都要验证
		if (uri.indexOf(".") < 0) return true;
		// 特殊的.do请求，都要验证
		for (Pattern filterSpecialCharacterActionSuffix : FILTER_SPECIAL_CHARACTER_ACTION_SUFFIX) {
			if (filterSpecialCharacterActionSuffix.matcher(uri).find()) return true;
		}
		// 酒店微信请求，都要验证
		if (uri.indexOf("/wchatHotel/") >= 0) return true;
		// 外链请求，都要验证
		if (uri.indexOf("/wailian/") >= 0) return true;

		return false;
	}

	public void init(FilterConfig arg0) throws ServletException {
		LOG.debug("ParameterSecurityCheckFilter过滤器启动。。。");
	}


	public void doFilter(ServletRequest request, ServletResponse response,
			FilterChain chain) throws IOException, ServletException {
		HttpServletRequest req = (HttpServletRequest) request;
		HttpServletResponse res = (HttpServletResponse) response;

		//LOG.debug("ParameterSecurityCheckInterceptor拦截 " + request.getRequestURI());
//		LOG.debug("拦截请求: " + req.getRequestURI());
//		LOG.debug("请求method: " + req.getMethod());

		boolean valid = processGetRequest(req, res);
		if (valid) valid = processPostRequest(req, res);
		if (valid) chain.doFilter(req, res);

	}

	public void destroy() {
		LOG.debug("ParameterSecurityCheckFilter过滤器停止。。。");
	}

	private boolean processGetRequest(HttpServletRequest req, HttpServletResponse res) {
		boolean valid = true;
		try {
			Map<String, String[]> paramMap = req.getParameterMap();
			Iterator<Map.Entry<String, String[]>> iterator = paramMap.entrySet().iterator();
			while(iterator.hasNext()) {
				Map.Entry<String, String[]> entry = iterator.next();
				String URI = req.getRequestURI();
				for(String val : entry.getValue()) {
					int flag = checkString(entry.getKey(), val, URI);
					if(flag != 0) {
						LOG.debug("find invalid param name: " + entry.getKey() +", value: " + val + ", flag: " + flag);
						Map<String, Object> resultMap = new HashMap<>();
						resultMap.put("flag", flag);
						resultMap.put("success", false);
						/*resultMap.put("uri", URI);
						resultMap.put("invalid_param_name", entry.getKey());*/
						//resultMap.put("invalid_param_value", val);
						/*resultMap.put("message_main", "参数中存在非法的字符或字符串！请参考com.hzqy.web.interceptor.ParameterSecurityCheckInterceptor");*/
						resultMap.put("result", "存在非法字符");

						res.setContentType("text/html;charset=utf-8");
						PrintWriter out = res.getWriter();
						out.print(JSONObject.toJSONString(resultMap));
						out.close();
						valid = false;
						break;
					}
				}
				if (!valid) break;
			}
		} catch (Exception e) {
		}
		return valid;
	}

	private boolean processPostRequest(HttpServletRequest req, HttpServletResponse res) {
		boolean valid = true;
		try {
			String body = getStringFromInputStream(req.getInputStream());
			if (StringUtils.isNotEmpty(body)) {
				int flag = checkPostString(body, req.getRequestURI());
				if(flag != 0) {
					LOG.debug("find invalid post param value: " + body + ", flag: " + flag);
					Map<String, Object> resultMap = new HashMap<>();
					resultMap.put("flag", flag);
					resultMap.put("success", false);
					//resultMap.put("uri", req.getRequestURI());
					//resultMap.put("invalid_param_value", body);
					/*resultMap.put("message_main", "参数中存在非法的字符或字符串！请参考com.hzqy.web.interceptor.ParameterSecurityCheckInterceptor");*/
					resultMap.put("result", "存在非法字符");

					res.setContentType("text/html;charset=utf-8");
					PrintWriter out = res.getWriter();
					out.print(JSONObject.toJSONString(resultMap));
					out.close();
					valid = false;
				}
			}
		} catch (Exception e) {
		}
		return valid;
	}

	public static String getStringFromInputStream(InputStream inputStream) {
		StringBuffer str = new StringBuffer();
		BufferedReader br = new BufferedReader(new InputStreamReader(inputStream));
		try {
			String temp;
			while ((temp = br.readLine()) != null) {
				str.append(temp);
			}
		} catch (IOException e) {
		} finally {
			close(inputStream);
		}
		return str.toString();
	}

	private static final void close(Closeable closeable) {
		if (closeable != null) {
			try {
				closeable.close();
			} catch (IOException e) {
			}
		}
	}


	public static void main(String[] args) {
		System.out.println(Pattern.compile("[()（）<>/\\\\{}\\-+='\";\\[\\]|&$#@.。]", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL).matcher("/").find());
	}

}
